package org.dragon.controllers;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthGiteeRequest;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.dragon.commons.*;
import org.dragon.models.*;
import org.dragon.services.AccountService;
import org.dragon.services.ConfigService;
import org.dragon.services.CredentialService;
import org.dragon.services.SessionAccountService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Author:     Zhao Yan
 * DateTime:   2023/5/17 11:08
 */
@RestController
@RequestMapping("/oauth")
public class OAuthController {

    @Resource
    private CredentialService credentialService;

    @Resource
    private SessionAccountService sessionAccountService;

    @Resource
    private AccountService accountService;

    @Resource
    private ConfigService configService;

    // 1、渲染 Gitee 登录窗口
    @GetMapping("/render/gitee")
    public void render(HttpServletResponse response) throws IOException {
        AuthRequest authRequest = getGiteeAuthRequest();
        String url = authRequest.authorize(AuthStateUtils.createState());
//        System.out.println(url);
        response.sendRedirect(url);
    }

    // 2、在刚刚的登录窗口中登录成功后，携带code和state信息返回
    @Transactional
    @GetMapping("/callback/gitee")
    public void callbackGitee(AuthCallback callback, HttpServletResponse response) throws IOException {
        AuthRequest authRequest = getGiteeAuthRequest();
        AuthResponse result = authRequest.login(callback);

        if (result.ok()) {
            Object data = result.getData();

            if (data instanceof AuthUser authUser) {
                String sourceId = authUser.getUuid();
                String source = "gitee";
                String token = Const.uuid();

                Credential credential = credentialService.findCredential(source, sourceId);

                System.out.println("credential: " + credential);

                RedisSessionAccount redisSessionAccount = new RedisSessionAccount();

                if (credential == null) {
                    // 从未注册
                    credential = new Credential();
                    credential.setSource(source);
                    credential.setSourceId(sourceId);
                    credential.setSourceNick(authUser.getNickname());

                    // 存数据库
                    credentialService.save(credential);
                }

                String accountId = credential.getAccountId();

                redisSessionAccount.credential = credential;
                redisSessionAccount.account = accountService.findById(accountId);
                redisSessionAccount.roles = accountService.findRoles(accountId);

                // 计算超时时间
                long timeout = Const.TEMPORARY_AUTH_DURATION;

                if (redisSessionAccount.account != null)
                    timeout = Const.AUTHORIZED_DURATION;

//                System.out.println(redisSessionAccount);

                // 存 redis
                sessionAccountService.save(token, redisSessionAccount, timeout);

                // 重定向到前端
                response.sendRedirect(configService.getAppConfig().frontEndUrl + "/oauth?token=" + token);
            }
        }

        Web.write(response,
                "<html><body><h1>Login Failed</h1></body></html>",
                HttpStatus.UNAUTHORIZED);
    }

    @PostMapping("/token/refresh")
    public Object refreshToken(HttpServletRequest request) {
        RedisSessionAccount query = sessionAccountService.query(request);
        String token = Web.getToken(request);
        sessionAccountService.save(token, query, Const.AUTHORIZED_DURATION);
        return JsonResult.ok(token);
    }

    @GetMapping("/info")
    public Object info(HttpServletRequest request) {
        RedisSessionAccount query = sessionAccountService.query(request);
        AccountInfo accountInfo = new AccountInfo();
        accountInfo.id = query.account.getId();
        accountInfo.name = query.account.getNick();

        if (query.roles != null) {
            List<String> roles = new ArrayList<>();
            Set<String> permissions = new HashSet<>();

            for (RedisSessionRole r : query.roles) {
                roles.add(r.role.getCode());

                if (r.permissions != null)
                    for (Permission permission : r.permissions)
                        if (permission != null)
                            permissions.add(permission.getCode());
            }

            accountInfo.roles = roles;
            accountInfo.permissions = permissions.stream().toList();
        }

        return accountInfo;
    }

    @PostMapping("/email/code")
    public Object sendEmailCode(HttpServletRequest request, @RequestBody RedisSessionEmail email) {
        RedisSessionAccount sessionAccount = sessionAccountService.query(request);

        if (sessionAccount.email != null && !sessionAccount.email.canSendCode())
            throw new ApplicationException(Errors.OperateTooFrequent);

        email.setCode(Const.randomEmailCode());
        email.setLastSentTime(Times.getTime(LocalDateTime.now()));

        System.out.println("Email Code: " + email.getCode());

        // TODO: 发送邮件

        sessionAccount.email = email;
        sessionAccountService.save(Web.getToken(request), sessionAccount,
                Const.TEMPORARY_AUTH_DURATION);

        return JsonResult.ok();
    }

    @Transactional
    @PostMapping("/email/bind")
    public Object bindEmail(HttpServletRequest request, @RequestBody RedisSessionEmail email) {
        RedisSessionAccount sessionAccount = sessionAccountService.query(request);

        if (sessionAccount.email == null || sessionAccount.credential == null)
            throw new ApplicationException(Errors.InvalidOperation);

        if (sessionAccount.email.validate(email.getCode())) {
            String accountId = sessionAccount.credential.getAccountId();

            // 已经存在 account
            if (StringUtils.hasText(accountId))
                throw new ApplicationException(Errors.InvalidOperation);

            Account account = accountService.findByEmail(
                    sessionAccount.email.getEmail());
            Credential credential = sessionAccount.credential;

            if (account == null) {
                // 注册新账户来绑定该邮箱
                account = new Account();
                account.setNick(Const.ulid());
                account.setEmail(sessionAccount.email.getEmail());
                accountService.save(account);
            }

            credential.setAccountId(account.getId());
            credentialService.updateAccountId(credential);

            sessionAccount.account = account;
            sessionAccount.email = null;
            sessionAccount.credential = credentialService.findCredential(credential.getId());

            sessionAccountService.save(Web.getToken(request),
                    sessionAccount, Const.AUTHORIZED_DURATION);

            return JsonResult.ok();
        }

        return JsonResult.fail(Errors.InvalidEmailCode);
    }

    private AuthRequest getGiteeAuthRequest() {
        AppConfig appConfig = configService.getAppConfig();
        return new AuthGiteeRequest(AuthConfig.builder().clientId(appConfig.giteeConfig.clientId).clientSecret(appConfig.giteeConfig.clientSecret).redirectUri(appConfig.giteeConfig.redirectUri).build());
    }
}
